#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
//#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LEDDEV_CNT    	1               		/* 设备号长度  	*/
#define LEDDEV_NAME     	"dtsplatled"    	/* 设备名字     	*/
#define LEDOFF          	0
#define LEDON           	1

// leddev 设备结构体
struct leddev_dev{
  dev_t devid; // 设备号
  struct cdev cdev; // cdev
  struct class* class; // 类
  struct device* device; // 设备
  struct device_node* node; // LED设备节点
  int gpio_led; // led灯gpio标号
};

struct leddev_dev leddev; // led设备

static const struct of_device_id led_of_match[] = {
  {.compatible = "alientek,led"},
  {}
};

static int led_gpio_init(struct device_node* nd){
  int ret;

  // 从设备树中获取gpio
  leddev.gpio_led = of_get_named_gpio(nd, "led-gpio", 0);
  if(!gpio_is_valid(leddev.gpio_led)){
    printk("failed to get gpio led\n");
    return -1;
  }

  // 申请使用gpio
  ret = gpio_request(leddev.gpio_led, "LED");
  if(ret){
    printk("failed to request led-gpio");
    return ret;
  }
  // 将gpio设置为输出模式并设置初始化电平
  gpio_direction_output(leddev.gpio_led, 0);

  return 0;
}

static int led_open(struct inode *inode, struct file *filp)
{
    return 0;
}

void led_switch(u8 sta)
{
    if (sta == LEDON )
        gpio_set_value(leddev.gpio_led, 1);
    else if (sta == LEDOFF)
        gpio_set_value(leddev.gpio_led, 0);
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;

    retvalue = copy_from_user(databuf, buf, cnt);
    if(retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }
    
    ledstat = databuf[0];
    if (ledstat == LEDON) {
        led_switch(LEDON);
    } else if (ledstat == LEDOFF) {
        led_switch(LEDOFF);
    }
    return 0;
}

static struct file_operations led_fops = {
  .owner = THIS_MODULE,
  .open = led_open,
  .write = led_write,
};

static int led_probe(struct platform_device* pdev){ // 驱动与设备匹配后此函数会执行
  int ret;
  printk("led driver and device was matched");

  // 初始化led
  ret = led_gpio_init(pdev->dev.of_node);
  if(ret < 0) return ret;

  /*1.设置设备号*/
  ret = alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
  if(ret < 0){
    printk("alloc_chrdev_region error\n");
    goto free_gpio;
  }
  /*2.初始化cdev*/
  leddev.cdev.owner = THIS_MODULE;
  cdev_init(&leddev.cdev, &led_fops);

  /*3.添加一个cdev*/
  ret = cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
  if(ret < 0)
    goto del_unregister;
  
  /*4.创建类*/
  leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
  if(IS_ERR(leddev.class)){
    goto del_cdev;
  }

  /*5.创建设备*/
  leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
  if(IS_ERR(leddev.device)){
    goto destroy_class;
  }

  return 0;
destroy_class:
  class_destroy(leddev.class);
del_cdev:
  cdev_del(&leddev.cdev);
del_unregister:
  unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
free_gpio:
  gpio_free(leddev.gpio_led);
  return -1;

}

static int led_remove(struct platform_device *dev)
{
    gpio_set_value(leddev.gpio_led, 0);/* 卸载驱动的时候关闭LED */
    gpio_free(leddev.gpio_led); 		/* 注销GPIO 	*/
    cdev_del(&leddev.cdev);             	/*  删除cdev 	*/
    unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
    device_destroy(leddev.class, leddev.devid); /* 注销设备 */
    class_destroy(leddev.class); 		/* 注销类 */
    return 0;
}

MODULE_DEVICE_TABLE(of, led_of_match);

// 结构体
static struct platform_driver led_driver = {
  .driver = {
    .name = "rk3568-led",   // 驱动名字
    .of_match_table = led_of_match,  // 设备树匹配表
  },
  .probe = led_probe,
  .remove = led_remove,
};


static int __init leddriver_init(void){
  return platform_driver_register(&led_driver);
}

static void __exit leddriver_exit(void){
  platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Narnat");